/*
 * Copyright 2015-2016 fududu.org Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.fududu.oauth2;

import com.google.common.collect.ImmutableMap;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import static com.google.common.base.Preconditions.checkState;

/**
 * @author Fuchun
 * @since 1.0
 */
public class DefaultAccessTokenRequest extends AbstractOAuth2Request implements AccessTokenRequest, Serializable {

    private static final long serialVersionUID = -5132288878268339244L;

    public static DefaultAccessTokenRequest authzCode(
            String clientId, String clientSecret, String redirectUri, String code, String scope) {
        return new DefaultAccessTokenRequest(clientId, clientSecret, redirectUri,
                scope, OAuth2.AUTHORIZATION_CODE, code, null, null);
    }

    public static DefaultAccessTokenRequest password(
            String clientId, String clientSecret, String username, String password, String scope) {
        return new DefaultAccessTokenRequest(clientId, clientSecret, null,
                scope, OAuth2.PASSWORD, null, username, password);
    }

    public static DefaultAccessTokenRequest clientCredentials(
            String clientId, String clientSecret, String scope) {
        return new DefaultAccessTokenRequest(clientId, clientSecret, null,
                scope, OAuth2.CLIENT_CREDENTIALS, null, null, null);
    }

    private String grantType;
    private String code;
    private String username;
    private String password;

    public DefaultAccessTokenRequest() {
        super();
    }

    public DefaultAccessTokenRequest(String queryString) {
        super(queryString);
    }

    public DefaultAccessTokenRequest(
            String clientId, String clientSecret, String redirectUri, String scope, String grantType,
            String code, String username, String password) {
        super(clientId, clientSecret, redirectUri, scope);
        this.grantType = grantType;
        this.code = code;
        this.username = username;
        this.password = password;
    }

    @Override
    protected Map<String, String> parseQueryString() {
        Map<String, String> qsMap = super.parseQueryString();
        if (qsMap.isEmpty()) {
            return qsMap;
        }
        grantType = qsMap.get(OAuth2.GRANT_TYPE);
        code = qsMap.get(OAuth2.CODE);
        username = qsMap.get(OAuth2.USERNAME);
        password = qsMap.get(OAuth2.PASSWORD);
        if (customParams != null && customParams.size() > 0) {
            customParams.clear();
        }

        if (grantType.length() > 0) {
            switch (grantType) {
                case OAuth2.AUTHORIZATION_CODE:
                    qsMap.forEach((k, v) -> {
                        if (!OAuth2.CODE_AT_REQUEST_PARAMS.contains(k)) {
                            initCustomParams().put(k, v);
                        }
                    });
                    break;
                case OAuth2.PASSWORD:
                    qsMap.forEach((k, v) -> {
                        if (!OAuth2.PASSWD_AT_REQUEST_PARAMS.contains(k)) {
                            initCustomParams().put(k, v);
                        }
                    });
                    break;
                case OAuth2.CLIENT_CREDENTIALS:
                    qsMap.forEach((k, v) -> {
                        if (!OAuth2.CLIENT_AT_REQUEST_PARAMS.contains(k)) {
                            initCustomParams().put(k, v);
                        }
                    });
                    break;
            }
        }
        return qsMap;
    }

    protected Map<String, String> initCustomParams() {
        if (customParams == null) {
            customParams = new LinkedHashMap<>();
        }
        return customParams;
    }

    @Override
    public String getGrantType() {
        return grantType;
    }

    public void setGrantType(String grantType) {
        this.grantType = grantType;
    }

    @Override
    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String getQueryString() {
        if (queryString != null) {
            return queryString;
        }
        checkState(grantType != null, "grantType value must not be null");
        Map<String, String> keyValueMap = new LinkedHashMap<>();
        keyValueMap.put(OAuth2.GRANT_TYPE, grantType);
        switch (grantType) {
            case OAuth2.AUTHORIZATION_CODE:
                keyValueMap.put(OAuth2.CODE, code);
                keyValueMap.put(OAuth2.REDIRECT_URI, OAuth2Request.encodeUrl(redirectUri));
                break;
            case OAuth2.PASSWORD:
                keyValueMap.put(OAuth2.USERNAME, username);
                keyValueMap.put(OAuth2.PASSWORD, password);
                keyValueMap.put(OAuth2.SCOPE, scope);
                break;
            case OAuth2.CLIENT_CREDENTIALS:
                keyValueMap.put(OAuth2.SCOPE, scope);
                break;
            default:
                throw new IllegalArgumentException("unsupported_grant_type");
        }
        keyValueMap.put(OAuth2.CLIENT_ID, clientId);
        keyValueMap.put(OAuth2.CLIENT_SECRET, clientSecret);
        queryString = QS_JOINER.join(keyValueMap);
        return queryString;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DefaultAccessTokenRequest)) return false;
        if (!super.equals(o)) return false;

        DefaultAccessTokenRequest that = (DefaultAccessTokenRequest) o;

        return Objects.equals(grantType, that.grantType) &&
                Objects.equals(code, that.code) &&
                Objects.equals(username, that.username) &&
                Objects.equals(password, that.password);
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + (grantType != null ? grantType.hashCode() : 0);
        result = 31 * result + (code != null ? code.hashCode() : 0);
        result = 31 * result + (username != null ? username.hashCode() : 0);
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return toStringHelper(ImmutableMap.of("grantType", getGrantType()),
                ImmutableMap.of(
                        OAuth2.CODE, getCode(),
                        OAuth2.USERNAME, getUsername(),
                        OAuth2.PASSWORD, getPassword()
                )).toString();
    }
}
